/*
 * $RCSfile: DefaultExecutor.java,v $$
 * $Revision: 1.1  $
 * $Date: 2013-2-19  $
 *
 * Copyright (C) 2008 Skin, Inc. All rights reserved.
 *
 * This software is the proprietary information of Skin, Inc.
 * Use is subject to license terms.
 */
package com.skin.ayada.template;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import com.skin.ayada.html.Document;
import com.skin.ayada.html.Node;
import com.skin.ayada.html.NodeType;
import com.skin.ayada.runtime.ExpressionContext;
import com.skin.ayada.runtime.JspWriter;
import com.skin.ayada.runtime.PageContext;
import com.skin.ayada.runtime.TagFactory;
import com.skin.ayada.tagext.BodyContent;
import com.skin.ayada.tagext.BodyTag;
import com.skin.ayada.tagext.IterationTag;
import com.skin.ayada.tagext.Tag;
import com.skin.ayada.util.Stack;
import com.skin.ayada.util.TagUtil;

/**
 * <p>Title: DefaultExecutor</p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2006</p>
 * @author xuesong.net
 * @version 1.0
 */
public class DefaultExecutor
{
    /**
     * @param template
     * @param pageContext
     */
    public static void execute(final Template template, final PageContext pageContext)
    {
        synchronized(template)
        {
            execute(template.getDocument(), pageContext);
        }
    }

    /**
     * @param document
     * @param pageContext
     */
    public static void execute(final Document document, final PageContext pageContext)
    {
        List<Node> childNodes = document.getChildNodes();

        if(childNodes != null && childNodes.size() > 0)
        {
            for(Node child : childNodes)
            {
                evaluate(child, pageContext);
            }
        }

        try
        {
            pageContext.getOut().flush();
        }
        catch(IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    /**
     * @param element
     * @throws IOException
     */
    public static void evaluate(final Node element, final PageContext pageContext)
    {
        Node node;
        TagEntry tagEntry;
        JspWriter out;
        Stack<TagEntry> stack = new Stack<TagEntry>();
        stack.push(new TagEntry(element, 0));
        ExpressionContext expressionContext = pageContext.getExpressionContext();

        while((tagEntry = stack.poll()) != null)
        {
            out = pageContext.getOut();
            node = tagEntry.getNode();

            try
            {
                if(node.getNodeType() == NodeType.TEXT)
                {
                    out.write(node.toString());
                    continue;
                }

                if(node.getNodeType() == NodeType.COMMENT)
                {
                    out.write(node.toString());
                    continue;
                }

                if(node.getNodeType() == NodeType.EXPRESSION)
                {
                    Object value = expressionContext.evaluate(node.toString());

                    if(value != null)
                    {
                        out.write(value.toString());
                    }

                    continue;
                }

                if(tagEntry.getStatus() == 0 || tagEntry.getStatus() == 2)
                {
                    int flag = startTag(stack, tagEntry, pageContext, expressionContext);

                    if(flag == 999)
                    {
                        // break;
                    }
                }
                else
                {
                    int flag = endTag(stack, tagEntry, pageContext);

                    if(flag == 999)
                    {
                        // break;
                    }
                }
            }
            catch(Exception e)
            {
                throw new RuntimeException(toString("Exception at ", node), e);
            }
        }
    }

    /**
     * @param stack
     * @param tagEntry
     * @param pageContext
     */
    private static int startTag(Stack<TagEntry> stack, TagEntry tagEntry, PageContext pageContext, ExpressionContext expressionContext)
    {
        int flag = 0;
        Tag tag = null;
        Node node = tagEntry.getNode();

        if(tagEntry.getStatus() == 0)
        {
            // create & init
            tag = TagFactory.create(pageContext, node.getNodeName());
            tagEntry.setTag(tag);
            tag.setPageContext(pageContext);

            if(tagEntry.getParent() != null)
            {
                tag.setParent(tagEntry.getParent().getTag());
            }

            TagUtil.setAttributes(tag, node.getAttributes(), expressionContext);
            flag = tag.doStartTag();
            tagEntry.setStartTagResult(flag);
        }
        else
        {
            tag = tagEntry.getTag();
            flag = tagEntry.getStartTagResult();
        }

        tagEntry.setStatus(1);
        stack.push(tagEntry);

        if(flag != Tag.SKIP_BODY)
        {
            if(flag != Tag.EVAL_BODY_INCLUDE)
            {
                if(tag instanceof BodyTag)
                {
                    BodyTag bodyTag = (BodyTag)tag;
                    BodyContent bodyContent = (BodyContent)(pageContext.pushBody());
                    bodyTag.setBodyContent(bodyContent);
                    bodyTag.doInitBody();
                }
            }

            /*
            List<Node> childNodes = node.getChildNodes();
            List<TagEntry> childEntrys = tagEntry.getChildNodes();
            if(childNodes.size() > 0 && childEntrys.size() < 1)
            {
                for(int i = 0, size = childNodes.size(); i < size; i++)
                {
                    TagEntry childTagEntry = new TagEntry(childNodes.get(i), 0);
                    childTagEntry.setParent(tagEntry);
                    childEntrys.add(childTagEntry);
                }
            }

            if(childEntrys.size() > 0)
            {
                for(int i = childEntrys.size() - 1; i > -1; i--)
                {
                    TagEntry childEntry = childEntrys.get(i);
                    stack.push(childEntry);
                }
            }*/

            List<Node> childNodes = node.getChildNodes();

            if(childNodes.size() > 0)
            {
                for(int i = childNodes.size() - 1; i > -1; i--)
                {
                    Node childNode = childNodes.get(i);
                    TagEntry childTagEntry = new TagEntry(childNode, 0);
                    childTagEntry.setParent(tagEntry);
                    stack.push(childTagEntry);
                }
            }
        }

        return flag;
    }

    /**
     * @param stack
     * @param tagEntry
     * @param pageContext
     */
    private static int endTag(Stack<TagEntry> stack, TagEntry tagEntry, PageContext pageContext)
    {
        Tag tag = tagEntry.getTag();
        IterationTag iterationTag = null;

        if(tag instanceof IterationTag)
        {
            iterationTag = (IterationTag)tag;
        }

        if(iterationTag != null)
        {
            int flag = iterationTag.doAfterBody();

            if(flag == BodyTag.EVAL_BODY_AGAIN)
            {
                tagEntry.setStatus(2);
                stack.push(tagEntry);
                return flag;
            }
            else
            {
                int startTagResult = tagEntry.getStartTagResult();

                if(startTagResult != Tag.SKIP_BODY)
                {
                    if(startTagResult != Tag.EVAL_BODY_INCLUDE)
                    {
                        if(tag instanceof BodyTag)
                        {
                            pageContext.popBody();
                        }
                    }
                }
            }
        }

        return tag.doEndTag();
    }

    /**
     * @param node
     * @return String
     */
    private static String toString(String prefix, Node node)
    {
        StringBuilder buffer = new StringBuilder();
        buffer.append(prefix);
        buffer.append("line #");
        buffer.append(node.getLineNumber());
        buffer.append(" ");

        if(node.getNodeType() == NodeType.TEXT)
        {
            buffer.append(node.toString());
            return buffer.toString();
        }

        if(node.getNodeType() == NodeType.COMMENT)
        {
            buffer.append(node.toString());
            return buffer.toString();
        }

        if(node.getNodeType() == NodeType.EXPRESSION)
        {
            buffer.append("${");
            buffer.append(node.toString());
            buffer.append("}");
            return buffer.toString();
        }

        buffer.append("<");
        buffer.append(node.getNodeName());
        Map<String, String> attributes = node.getAttributes();

        if(attributes != null && attributes.size() > 0)
        {
            for(Map.Entry<String, String> entrySet : attributes.entrySet())
            {
                buffer.append(" ");
                buffer.append(entrySet.getKey());
                buffer.append("=\"");
                buffer.append(entrySet.getValue());
                buffer.append("\"");
            }
        }

        if(node.getNodeType() == NodeType.PAIR_CLOSED)
        {
            buffer.append(">...");
            buffer.append("</");
            buffer.append(node.getNodeName());
            buffer.append(">");
        }
        else
        {
            buffer.append("/>");
        }
        return buffer.toString();
    }
}
